package tool;

import genome.BG;
import genome.CGP;
import genome.GGT;
import genome.Genotype;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;

import coevolution.GGTGenerator;

public class Utility<H1,H2> {
//H1 and H2 are generic types used hash table operations
	
	//Contains general use methods for IO, population manipulation and stats.
	
	//Need to use the below static routines for manipulating arrays of genotypes to ensure deep copy applied correctly.
	
	public static int binStrToInt(String s){
		int x = 0; int n=s.length()-1;
		for(int i=0;i<s.length();i++){
			Integer xi = Integer.parseInt(s.substring(i, i+1));
			x = x+ xi*(int)Math.pow(2,n);
			n = n-1;
		}
		return x;
	}
	
	//from Stack Overflow, author pyrolistical
	public static double roundToSignificantFigures(double num, int n) {
	    if(num == 0) {
	        return 0;
	    }

	    final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
	    final int power = n - (int) d;

	    final double magnitude = Math.pow(10, power);
	    final long shifted = Math.round(num*magnitude);
	    return shifted/magnitude;
	}
	
	public static double[] distMatrixToVector(double[][] M){
		//takes in a square symmetric distance matrix and converts it to a vector version from the upper triangle
		if(M.length!=M[0].length){
			try {
				throw new Exception();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		int k=0;
		double[] v = new double[((M.length*M.length)-M.length)/2];
		for(int i=0;i<M.length;i++){
			for (int j = i+1; j < M.length; j++) {
				v[k] = M[i][j];
				k=k+1;
			}
		}
		return v;
	}
	
	public static double[] matrixToVector(double[][] M){
		double[] v = new double[M.length*M[0].length];
		int k=0;
		for(int i=0;i<M.length;i++){
			for (int j = 0; j < M[i].length; j++) {
				v[k] = M[i][j];
				k = k+1;
			}
		}
		return v;
	}
	
	public static int indexOfMostSimilar(int[] x, int[] y){
		//assumes x and y are the same length
		
		int bestDifference = Integer.MAX_VALUE;
		int index = 0;
		for (int i = 0; i < x.length; i++) {
			int diff = Math.abs(x[i]-y[i]);
			if(diff<bestDifference){
				index = i; bestDifference = diff; //returns index of first element to have the 
			}
			
		}
		return index;
	}
	
	public static int intransitive2Game(int[] x, int[] y){
		//assumes x and y are two elements
		int diff1 = Math.abs(x[0]-y[0]); int diff2=Math.abs(x[1]-y[1]);
		if(diff1>diff2 && x[1]>y[1]){
			return 1;
		}
		else {
			if(diff2>diff1 && x[0]>y[0]){
				return 1;
			}
			else {
				return 0;
			}
		}
	}
	
	public static int compareOnOne(double[] x, double[] y){
		double mx[] = Utility.mx(y);
		int yMax = (int)mx[1];
		if(x[yMax]>mx[0]){
			return 1;
		}
		else {
			return 0;
		}
	}
	
	public static void dumpArrayList(ArrayList A, String path){
			PrintWriter p = null;
			try {
				p = new PrintWriter(new FileWriter(new File(path)));
			} catch (IOException e) {
				e.printStackTrace();
			}
			Iterator k = A.iterator();
			while(k.hasNext()){
				p.println(k.next());
			}
			p.close();
	}
	
	public static String doubleArrayToString(double[] d){
		String s="";
		
		for (int i = 0; i < d.length; i++) {
			s = s+" "+d[i];
		}
		return s;
	}
	
	public static int[] clearArray(int[] x){
		for(int i=0;i<x.length;i++){
			x[i] = 0;
		}
		return x;
	}
	
	public static boolean contains(ArrayList<Genotype> P, Genotype g){
		for(Genotype p : P){
			if(p.equals(g)){return true;}
		}
		return false;
	}

	public static ArrayList<Genotype> copy(ArrayList<Genotype> p){
		ArrayList<Genotype> c = new ArrayList<Genotype>();
		for(Genotype g : p){
			c.add(g.getCopy());
		}
		return c;
	}

	/*public static double[][] getHist(int[] x, int nBin){
		int max[] = mx(x); int min[] = min(x);
		double div = (double)(max[0]-min[0])/(double)nBin;
		double[][] hist = new double[nBin][2];
		
		for (int i = 0; i < hist.length; i++) {
			hist[i][0] = min[0]+div*i;
		}
		
		for(int i=0;i<x.length;i++){
			System.out.println(x[i]);
			
			hist[incr][1] = hist[incr][1] + 1;
		}
		return hist;
	}*/
	
	
	public static Hashtable<String, String> copyHash(Hashtable<String, String> h) {
		Hashtable<String,String> h2 = new Hashtable<String,String>();
		Set<String> keys = h.keySet();
		Collection<String> vals = h.values();
		ArrayList<String> k2 = new ArrayList<String>();
		ArrayList<String> v2 = new ArrayList<String>();
		for(String s : keys){
			String s2 = new String(s);
			k2.add(s2);
		} //explicit copy of key set, can't modify keys directly because this will change the original hash
		
		for(String s: vals){
			String s2 = new String(s);
			v2.add(s2); //explicit copying of values
		}
		
		for (int i = 0; i < h.size(); i++) {
			String key = k2.get(i); String v = v2.get(i);
			h2.put(key, v);
		}
		
		return h2;
	}

	public static void dump(String fName, double[] data){
		PrintWriter p = null;
		try{
			p = new PrintWriter(new FileWriter(new File(fName)));
		}
		catch(IOException e){
			System.out.println("Dump failed.");
			return;
		}
		for(int i=0;i<data.length;i++){
			p.println(""+data[i]);
		}
		p.close();
	}

	public static void dump(String fName, double[][] data){
		PrintWriter p = null;
		try{
			p = new PrintWriter(new FileWriter(new File(fName)));
		}
		catch(IOException e){
			System.out.println("Dump failed.");
			return;
		}
		for(int i=0;i<data.length;i++){
			for(int j=0;j<data[0].length;j++){
				p.print(data[i][j]+" ");
			}
			p.println();
		}
		p.close();
	}

	public static double[] getBitRep(int x, int y){
		//Map the X and Y values to bits
		String X = Integer.toBinaryString(x); String Y = Integer.toBinaryString(y);
		double[] XBit = new double[4]; double[] YBit = new double[4];
		
		int posX = XBit.length - X.length(); int posY = YBit.length -Y.length();
		for(int i=0;i<X.length();i++){
			XBit[posX+i] = Integer.parseInt(X.substring(i,i+1));
		}
		for(int i=0;i<Y.length();i++){
			YBit[posY+i] = Integer.parseInt(Y.substring(i,i+1));
		}
		
		//Concatenate to form 8 'bit' double array
		double[] eightBit = new double[XBit.length+YBit.length];
		for(int i=0;i<XBit.length;i++){
			eightBit[i] = XBit[i];
			eightBit[i+XBit.length] = YBit[i];
		}
		return eightBit;
	}

	public static double getFitnessVariance(ArrayList<Genotype> pop){
		
		int i=0;
		double[] f = new double[pop.size()];
		
		for(Genotype g : pop){ f[i] = g.RELATIVE_FITNESS; i++;}
		
		return variance(f);
	}

	public static int getIndex(ArrayList<Genotype> P, Genotype g){
		int i = 0;
		for(Genotype p:P){
			if(p.equals(g)){
				return i;
			}
			i++;
		}
		return -1;
	}

	public static Hashtable<String,String> getInverseHash(Hashtable<String,String> h){
		//takes in a map and produces the inverse
		
		Hashtable<String,String> h2 = new Hashtable<String,String>();
		Set<String> bin = h.keySet();
		for(String s : bin){
			String s2 = new String(s);
			String sVal = new String(h.get(s));
			h2.put(sVal,s2);
		}
		return h2;
		
	}

	public static int ham(Genotype g1, Genotype g2){
		int h = 0;
		for(int i=0;i<g1.size;i++){
			if(g1.genome[i]!=g2.genome[i]){
				h = h+1;
			}
		}
		return h;
	}

	public static int ham(int[] a, int[] b){
		int h = 0;
		for(int i=0;i<a.length;i++){
			if(a[i]!=b[i]){
				h = h+1;
			}
		}
		return h;
	}

	public static String intArrayToString(int[] x){
		String s = "";
		for (int i = 0; i < x.length; i++) {
			s = s+x[i];
		}
		return s;
	}

	public static String IntToBinStr(int x, int len){
		//replaces the method provided by Integer to give leading zeros up to value len.
		String bin = Integer.toBinaryString(x);
		String leadingZeros = "";
		for(int i=0;i<(len-bin.length());i++){
			leadingZeros = leadingZeros +"0";
		}
		bin = leadingZeros+bin;
		return bin;
	}

	public static int[] min(int[] x){
		//returns minimum value along array and index
		int s = Integer.MAX_VALUE; int index = 0;
		for (int i = 0; i < x.length; i++) {
			if(x[i]<s){
				s = x[i];
				index = i;
			}
		}
		int[] out = {s,index};
		return out;
	}

	public static int[] mx(int[] x){
		//returns maximum value along array and index
		int s = 0; int index = 0;
		for (int i = 0; i < x.length; i++) {
			if(x[i]>s){
				s = x[i];
				index = i;
			}
		}
		int[] out = {s,index};
		return out;
	}
	
	public static double[] mx(double[] x){
		//returns maximum value along array and index
		double s = 0; int index = 0;
		for (int i = 0; i < x.length; i++) {
			if(x[i]>s){
				s = x[i];
				index = i;
			}
		}
		double[] out = {s,index};
		return out;
	}

	public static boolean isEqualTo(ArrayList<Genotype> p1, ArrayList<Genotype> p2){
		if(p1.size()!=p2.size()){
			return false;
		}
		else {
			boolean equal = true;
			for(Genotype g : p1){
				equal = equal && contains(p2,g);
			}
			return equal;
		}
	}
	
	public static void writeMatToFile(String s, double[][] A){
		//assumes square mat
		PrintWriter p;
		try {
			p = new PrintWriter(new FileWriter(new File(s)));
		} catch (IOException e) {
			System.out.println("Failed to print "+s+" to file");
			e.printStackTrace();
			return;
		}
		for (int i = 0; i < A.length; i++) {
			for (int j = 0; j < A[0].length-1; j++) {
				p.print(A[i][j]+" ");
			}
			p.print(""+A[i][A[0].length-1]);
			p.println();
		}
		p.close();
	}

	public static void printMat(double[][] A){
		for (int i = 0; i < A.length; i++) {
			for (int j = 0; j < A.length-1; j++) {
				System.out.print(A[i][j]+" ");
			}
			System.out.print(""+A[i][A.length-1]);
			System.out.println("");
		}
	}

	public static void printPop(ArrayList<Genotype> p){
		System.out.println("------");
		for(Genotype g : p){
			String phenotype = g.toMathString();
			//String vec = intArrayToString(g.toVector());
			String vec = ((CGP)g).toSymbolicValues(2,0.0,Math.PI);
			System.out.println(g+" "+phenotype+" "+vec);
		}
		System.out.println("------");
	}

	public static void printRepType(PrintWriter pParam, Genotype g) {
		
		if(g instanceof GGT){
			pParam.println("GGT");
		}
	}

	public static ArrayList<Genotype> remove(ArrayList<Genotype> p1, ArrayList<Genotype> p2){
		ArrayList<Genotype> pMinus = copy(p1);
		for(Genotype g : p2){
			int index = getIndex(pMinus,g);
			if(index!=-1){pMinus.remove(index);
			}
		}
		return pMinus;
	}

	public static ArrayList<Genotype> remove(Genotype g,ArrayList<Genotype> p){
		ArrayList<Genotype> pMinus = copy(p);
		int index = getIndex(pMinus,g);
		if(index!=-1){
			pMinus.remove(index);
		}
		return pMinus;
	}

	public static int[] strToIntArray(String s){
		int[] x = new int[s.length()];
		for(int i=0;i<s.length();i++){
			x[i] = Integer.parseInt(s.substring(i, i+1));
		}
		return x;
	}

	public static ArrayList<Genotype> union(ArrayList<Genotype> p1, ArrayList<Genotype> p2){
		
		ArrayList<Genotype> pUnion = copy(p1);
		
		for(Genotype g : p2){
			if(!contains(p1,g)){
				pUnion.add(g);
			}
		}
		
		return pUnion;
	}

	public static ArrayList<Genotype> union(ArrayList<Genotype> p1, Genotype g){
		
		ArrayList<Genotype> pUnion = copy(p1);
		
		if(!contains(p1,g)){
			pUnion.add(g);
		}
		
		return pUnion;
	}

	public static double variance(double[] dat){
		
		if(dat.length <= 1){throw new NumberFormatException();}
		
		double mu2 = 0.0;
		double mu = 0.0;
		
		double n = dat.length;
		
		for(int i=0; i<n;i++){
			mu2 = mu2+ (Math.pow(dat[i],2));
			mu = mu+ dat[i];
		} 
		mu2 = mu2/n;
		mu = mu/n;
		
		return (mu2 - Math.pow(mu,2))*(n/(n-1));
	}

	public static double dg(BG g1, BG g2){
		//returns the probability of transforming g1 into g2 through stochastic mutation
		//for the moment let's assume both are an instance of BG, so remember this won't generalise
			int h = Utility.ham(g1,g2); double m = BG.MUTATION_RATE; double p = 1.0; //chance of transforming to particular value
			double x = Math.pow(m*p,h)*Math.pow((1-m),g1.size-h); 							// where factor one comes from BG
			return x;
		}
}
